#!/usr/bin/env bash
#
# @license Apache-2.0
#
# Copyright (c) 2017 The Stdlib Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Script for compiling WebAssembly for a provided absolute package path.
#
# Usage: compile_wasm <pkg>
#
# Arguments:
#
#   pkg      Package directory (absolute path).
#
#
# Environment Variables:
#
#   NODE            Command for running Node.js. Default: `node`.
#   NODE_PATH       Path for resolving node modules.
#   CLANG_COMPILER  LLVM Clang compiler. Default: `clang`.
#   CLANG_SYSROOT   Path to a C standard library.
#   EMCC_COMPILER   Emscripten C compiler. Default: `emcc`.
#   WASM2WAT        Command for converting WebAssembly binary format to text format. Default. `wasm2wat`.
#   WASM2JS         Command for converting WebAssembly binary format to JavaScript. Default. `wasm2js`.
#   SRC_FOLDER      Folder containing source files. Default: `src`.
#   INCLUDE         Includes (e.g., `-I /foo/bar -I /a/b`).
#   SOURCE_FILES    Source file list.
#   LIBRARIES       Linked libraries (e.g., `-lopenblas -lpthreads`).
#   LIBPATH         Library paths (e.g., `-L /foo/bar -L /a/b`).
#

# shellcheck disable=SC2181,SC2153


# VARIABLES #

# Set the package path:
pkg_path="$1"

# Define the command for running Node.js:
node_cmd="${NODE}"
if [[ -z "${node_cmd}" ]]; then
	node_cmd='node'
fi

# Define the node path:
node_path="${NODE_PATH}"

# Define the path to the LLVM `clang` compiler:
clang_compiler="${CLANG_COMPILER}"
if [[ -z "${clang_compiler}" ]]; then
	clang_compiler='clang'
fi

# Define the path to the Emscripten `emcc` compiler:
emcc_compiler="${EMCC_COMPILER}"
if [[ -z "${emcc_compiler}" ]]; then
	emcc_compiler='emcc'
fi

# Define the path to an executable for converting a WebAssembly binary to the WebAssembly text format:
wasm_to_wat="${WASM2WAT}"
if [[ -z "${wasm_to_wat}" ]]; then
	wasm_to_wat='wasm2wat'
fi

# Define the path to an executable for converting a WebAssembly binary to JavaScript:
wasm_to_js="${WASM2JS}"
if [[ -z "${wasm_to_js}" ]]; then
	wasm_to_js='wasm2js'
fi

# Define the folder containing source files:
src_folder="${SRC_FOLDER}"
if [[ -z "${src_folder}" ]]; then
	src_folder='src'
fi

# Define a path to a C standard library:
clang_sysroot="${CLANG_SYSROOT}"

# Define a list of external `include` directories:
include="${INCLUDE}"

# Define a list of external source files:
source_files="${SOURCE_FILES}"

# Define a list of libraries (e.g., `-lopenblas -lpthreads`):
libraries="${LIBRARIES}"

# Define a list of library paths (e.g., `-L /foo/bar -L /beep/boop`):
libpath="${LIBPATH}"


# FUNCTIONS #

# Defines an error handler.
#
# $1 - error status
on_error() {
	echo 'ERROR: An error was encountered during execution.' >&2
	cleanup
	exit "$1"
}

# Runs clean-up tasks.
cleanup() {
	echo '' >&2
}

# Prints a success message.
print_success() {
	echo 'Success!' >&2
}

# Prints usage information.
usage() {
	echo '' >&2
	echo 'Usage: compile_wasm <pkg>' >&2
	echo '' >&2
}

# Resolves external `include` directories.
#
# $1 - package directory
resolve_includes() {
	local includes
	local script
	local opts

	opts="{'wasm':true,'os':''}"

	# Generate the script for resolving external include directories:
	script='"'"var path = require('path'); var arr = require('@stdlib/utils/library-manifest')(path.join('$1','manifest.json'),${opts},{'basedir':'$1','paths':'posix'}).include; var str = ''; for (var i = 0; i < arr.length; i++){var p = path.resolve('$1', arr[i]); if (p.indexOf('$1') === 0) {continue;}; str += '-I '+p+' ';}; console.log(str.substring(0, str.length-1));"'"'

	# Resolve include directories:
	includes=$(eval NODE_PATH="${node_path}" "${node_cmd}" -e "${script}")

	echo "${includes}"
}

# Resolves external source files.
#
# $1 - package directory
resolve_source_files() {
	local source_files
	local script
	local opts

	opts="{'wasm':true,'os':''}"

	# Generate the script for resolving external source files:
	script='"'"var path = require('path'); var arr = require('@stdlib/utils/library-manifest')(path.join('$1','manifest.json'),${opts},{'basedir':'$1','paths':'posix'}).src; var str = ''; for (var i = 0; i < arr.length; i++){var p = path.resolve('$1', arr[i]); if (p.indexOf('$1') === 0) {continue;}; str += p+' ';}; console.log(str.substring(0, str.length-1));"'"'

	# Resolve files:
	source_files=$(eval NODE_PATH="${node_path}" "${node_cmd}" -e "${script}")

	echo "${source_files}"
}

# Resolves libraries.
#
# $1 - package directory
resolve_libraries() {
	local libraries
	local script
	local opts

	opts="{'wasm':true,'os':''}"

	# Generate the script for resolving libraries:
	script='"'"var path = require('path'); var arr = require('@stdlib/utils/library-manifest')(path.join('$1','manifest.json'),${opts},{'basedir':'$1','paths':'posix'}).libraries; var str = ''; for (var i = 0; i < arr.length; i++){str += arr[i]+' ';}; console.log(str.substring(0, str.length-1));"'"'

	# Resolve libraries:
	libraries=$(eval NODE_PATH="${node_path}" "${node_cmd}" -e "${script}")

	echo "${libraries}"
}

# Resolves library paths.
#
# $1 - package directory
resolve_libpaths() {
	local libpath
	local script
	local opts

	opts="{'wasm':true,'os':''}"

	# Generate the script for resolving library paths:
	script='"'"var path = require('path'); var arr = require('@stdlib/utils/library-manifest')(path.join('$1','manifest.json'),${opts},{'basedir':'$1','paths':'posix'}).libpath; var str = ''; for (var i = 0; i < arr.length; i++){var p = path.resolve('$1', arr[i]); str += '-L '+p+' ';}; console.log(str.substring(0, str.length-1));"'"'

	# Resolve library paths:
	libpath=$(eval NODE_PATH="${node_path}" "${node_cmd}" -e "${script}")

	echo "${libpath}"
}

# Compiles WebAssembly.
#
# $1 - source directory
compile() {
	cd "$1" && CLANG_COMPILER="${clang_compiler}" CLANG_SYSROOT="${clang_sysroot}" EMCC_COMPILER="${emcc_compiler}" WASM2WAT="${wasm_to_wat}" WASM2JS="${wasm_to_js}" INCLUDE="${include}" SOURCE_FILES="${source_files}" LIBRARIES="${libraries}" LIBPATH="${libpath}" make wasm 2>&1
	if [[ "$?" -ne 0 ]]; then
		echo 'Error when attempting to compile WebAssembly.' >&2
		return 1
	fi
	echo 'Successfully compiled WebAssembly.' >&2
	return 0
}


# MAIN #

# Main execution sequence.
main() {
	local src_dir="${pkg_path}/${src_folder}"

	if [[ -z "${include}" ]]; then
		echo 'Resolving external include directories...' >&2
		include=$(resolve_includes "${pkg_path}")
		if [[ "$?" -ne 0 ]]; then
			on_error 1
		fi
	fi
	if [[ -z "${source_files}" ]]; then
		echo 'Resolving external source files...' >&2
		source_files=$(resolve_source_files "${pkg_path}")
		if [[ "$?" -ne 0 ]]; then
			on_error 1
		fi
	fi
	if [[ -z "${libraries}" ]]; then
		echo 'Resolving libraries...' >&2
		libraries=$(resolve_libraries "${pkg_path}")
		if [[ "$?" -ne 0 ]]; then
			on_error 1
		fi
	fi
	if [[ -z "${libpath}" ]]; then
		echo 'Resolving library paths...' >&2
		libpath=$(resolve_libpaths "${pkg_path}")
		if [[ "$?" -ne 0 ]]; then
			on_error 1
		fi
	fi
	echo 'Compiling WebAssembly...' >&2
	compile "${src_dir}"
	if [[ "$?" -ne 0 ]]; then
		on_error 1
	fi
	print_success
	cleanup
	exit 0
}

# Handle arguments...
if [[ "$#" -eq 0 ]]; then
	usage
	exit 0
elif [[ "$#" -gt 1 ]]; then
	echo 'ERROR: unrecognized arguments. Must provide a package path.' >&2
	exit 1
fi

# Run main:
main
